package com.cliff.joinus;

import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.cybergarage.upnp.ArgumentList;
import org.cybergarage.upnp.Device;
import org.cybergarage.upnp.UPnP;
import org.cybergarage.upnp.std.av.renderer.AVTransport;
import org.cybergarage.upnp.std.av.server.object.item.ItemNode;
import org.cybergarage.xml.Node;
import org.cybergarage.xml.Parser;
import org.cybergarage.xml.ParserException;

import com.cliff.joinus.Constants.ITEMNODE_TYPE;
import com.cliff.joinus.dataobject.ImageItem;
import com.cliff.joinus.dataobject.MusicItem;
import com.cliff.joinus.dataobject.VideoItem;

import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;

public class RendererMediator {

	protected static final String TAG = "RendererMediator";

	protected String friendlyName = null;
	protected String ip = null;
	protected String sid = null;
	protected String udn = null;
	protected long seq = -1;
	protected Parser mParser = UPnP.getXMLParser();
	protected OnRendererStatusChangedListener mListener = null;
	protected ITEMNODE_TYPE currentPlayingType = null;
	protected int currentPlayingPos = 0;
	protected ArrayList<Node> mediaServerNodeList = null;
	protected ReadWriteLock lock = new ReentrantReadWriteLock();
	
	protected boolean autoPiloted = false;
	protected HandlerThread threadBackground = null;
	protected Handler handlerBackground = null;
	
	public static enum MEDIATOR_OPERATION {PLAYPAUSE, NEXT, PREV, SEEK, STOP};
	public static enum MEDIATOR_STATUS {PLAYING, TRANSFERING, STOPPED, PAUSED_PLAYBACK};
	public static final int CURRENT_POSITION = -1;

	protected String mAVTransportURI = null;
	protected String mCurrentMediaDuration = null;
	protected String mCurrentTrackDuration = null;
	protected String mCurrentTrackMetaData = null;
	protected String mCurrentTrackURI = null;
	protected String mTransportState = null;
//	protected String mAbsoluteCounterPosition = null;
//	protected String mAbsoluteTimePosition = null;
//	protected String mAVTransportURIMetaData = null;
//	protected String mCurrentPlayMode = null;
//	protected String mCurrentRecordQualityMode = null;
//	protected String mCurrentTrack = null;
//	protected String mCurrentTransportActions = null;
//	protected String mInstanceID = null;
//	protected String mNextAVTransportURI = null;
//	protected String mNextAVTransportURIMetaData = null;
//	protected String mNumberOfTracks = null;
//	protected String mPlaybackStorageMedium = null;
//	protected String mPossiblePlaybackStorageMedia = null;
//	protected String mPossibleRecordQualityModes = null;
//	protected String mPossibleRecordStorageMedia = null;
//	protected String mRecordMediumWriteStatus = null;
//	protected String mRecordStorageMedium = null;
//	protected String mRelativeCounterPosition = null;
//	protected String mRelativeTimePosition = null;
//	protected String mTransportPlaySpeed = null;
//	protected String mTransportStatus = null;

//	public static enum MEDIATOR_STATUS {CONTROLABLE, INPUT_PROCESSING, CONTROLLED_BY_OTHERS};
	
	protected Timer timer = new Timer();
	protected TimerTask positionTask = null;
	protected class PositionTimerTask extends TimerTask {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			if (null != mListener) {
				if (null == RendererMediator.this.currentPlayingType)
					return;
				
				if (ITEMNODE_TYPE.LOCAL_IMAGE == RendererMediator.this.currentPlayingType)
					return;

				ArgumentList argList = JoinusControlPoint.getInstance().getRendererPositionInfo();
				if (null != argList) {
					int progress = convertTimeStringToInt(argList.getArgument(AVTransport.RELTIME).getValue());
					String duration = argList.getArgument(AVTransport.TRACKDURATION).getValue();
					int max = convertTimeStringToInt(duration);
					
					updateListenerProgress(progress);
					updateListenerDuration(max, duration, null);
				}				
			}
		}		
	};
	
//	public RendererMediator(String udn, String sid, String friendlyName, String ip) {
//		Log.d(TAG, "RendererMediator():sid=" + sid + ";friendlyName="
//				+ friendlyName + ";ip=" + ip);
//		this.udn = udn;
//		this.sid = sid;
//		this.friendlyName = friendlyName;
//		this.ip = ip;
//	}
	
	public RendererMediator(Device dev) {
		this.udn = dev.getUDN();
		this.sid = null;
		this.seq = -1;
		this.friendlyName = dev.getFriendlyName();
		this.ip = dev.getSSDPPacket().getRemoteAddress();
	}

	public void dispose() {
		Log.d(TAG, "RendererMediator:dispose():sid=" + sid + ";friendlyName="
				+ friendlyName + ";ip=" + ip);
		timer.cancel();
		this.udn = null;
		this.sid = null;
		this.seq = -1;
		this.friendlyName = null;
		this.ip = null;
	}
	
	public String getFriendlyName() {
		return friendlyName;
	}
	
	public String getIp() {
		return ip;
	}
	
	public String getSid() {
		return sid;
	}
	
	public void setListener(OnRendererStatusChangedListener listener) {
		lock.writeLock().lock();
		this.mListener = listener;
		lock.writeLock().unlock();
		
		if (null == listener) {
			return;
		}
		
		//do you know what a list you are playing?
		if (null == currentPlayingType) {
			//for now, i don't know what to play, so init with all null;
			listener.updateDuration(0, "", "");
			listener.updateNextButton(false);
			listener.updatePlayPauseButton(false, true);
			listener.updatePrevButton(false);
			listener.updateProgress(0);
			listener.updateTitle("");
			return;
		}
		
		//i know what i'm playing here
		switch (currentPlayingType) {
		case LOCAL_IMAGE:
			initListenerLocalImage(listener);
			break;
		case LOCAL_AUDIO:
			initListenerLocalAudio(listener);
			break;
		case LOCAL_VIDEO:
			initListenerLocalVideo(listener);
			break;
		default:
			break;
		}
	}
	
	protected void initListenerLocalImage(OnRendererStatusChangedListener listener) {
		ArrayList<ImageItem> list = LocalMediaDataManager.getInstance().getImageList();
		int itemCount = list.size();
		ImageItem item = list.get(this.currentPlayingPos);
		
		listener.updatePlayPauseButton(false, true);

		listener.updateNextButton(this.currentPlayingPos<(itemCount-1));
		listener.updatePrevButton(this.currentPlayingPos>0);
		listener.updateProgress(this.currentPlayingPos);
		listener.updateTitle(item.filePathName);
		listener.updateDuration(itemCount-1, ""+itemCount, ""+(this.currentPlayingPos+1));		
	}

	protected void initListenerLocalAudio(OnRendererStatusChangedListener listener) {
		ArrayList<MusicItem> list = LocalMediaDataManager.getInstance().getMusicList();
		int itemCount = list.size();
		MusicItem item = list.get(this.currentPlayingPos);
		
		listener.updatePlayPauseButton(true, true);
		listener.updateNextButton(this.currentPlayingPos<(itemCount-1));
		listener.updatePrevButton(this.currentPlayingPos>0);
		listener.updateTitle(item.filePathName);
		listener.updateProgress(0);
		listener.updateDuration(0, "00:00", "00:00");		
	}

	protected void initListenerLocalVideo(OnRendererStatusChangedListener listener) {
		ArrayList<VideoItem> list = LocalMediaDataManager.getInstance().getVideoList();
		int itemCount = list.size();
		VideoItem item = list.get(this.currentPlayingPos);
		
		listener.updatePlayPauseButton(true, true);
		listener.updateNextButton(this.currentPlayingPos<(itemCount-1));
		listener.updatePrevButton(this.currentPlayingPos>0);
		listener.updateTitle(item.filePathName);
		listener.updateProgress(0);
		listener.updateDuration(0, "00:00:00", "00:00:00");		
	}

	public void updateStatus(String uuid, long seq, String name, String value) {
		Log.d(TAG, "updateStatus():name=" + name + ";value=" + value);
		
		if (null == this.sid)
			this.sid = uuid;
		
		if (seq < this.seq)
			return;
		this.seq = seq;
		
		
		Node node = null;
		try {
			node = mParser.parse(value);
		} catch (ParserException e) {
			//e.printStackTrace();
			Log.d(TAG, "updateStatus:ParserException="+e.toString());
			return;
		}

		if ("LastChange".equals(name)) {
			parseLastChange(node);
		}
	}
	
	protected void parseLastChange(Node node) {		
		if (null == node) return;
		Log.d(TAG, "parseLastChange");
		node = node.getNode("InstanceID");
		if (null == node) return;
		
		updateCurrentTrackURI(node.getNode("CurrentTrackURI"));		
		updateCurrentTrackDuration(node.getNode("CurrentTrackDuration"));
		updateCurrentMediaDuration(node.getNode("CurrentMediaDuration"));
		updateAVTransportURI(node.getNode("AVTransportURI"));		
		updateTransportState(node.getNode("TransportState"));
		updateCurrentTrackMetaData(node.getNode("CurrentTrackMetaData"));
	}
	
	protected void updateCurrentTrackURI(Node node) {
		if (null == node) return;
		mCurrentTrackURI = node.getAttributeValue("val");
		Log.d(TAG, "mCurrentTrackURI="+mCurrentTrackURI);
	}

	protected void updateCurrentTrackDuration(Node node) {
		if (null == node) return;
		mCurrentTrackDuration = node.getAttributeValue("val");
		Log.d(TAG, "mCurrentTrackDuration="+mCurrentTrackDuration);
	}
	
	protected void updateCurrentMediaDuration(Node node) {
		if (null == node) return;
		mCurrentMediaDuration = node.getAttributeValue("val");
		Log.d(TAG, "mCurrentMediaDuration="+mCurrentMediaDuration);
	}

	protected void updateAVTransportURI(Node node) {
		if (null == node) return;
		mAVTransportURI = node.getAttributeValue("val");
		Log.d(TAG, "mAVTransportURI="+mAVTransportURI);
	}

	protected void updateTransportState(Node node) {
		if (null == node) return;
		String oldState = mTransportState;
		mTransportState = node.getAttributeValue("val");
		Log.d(TAG, "mTransportState="+mTransportState);
		onTransportStateChanged(oldState, mTransportState);
	}
	
	protected void updateCurrentTrackMetaData(Node node) {
		if (null == node) return;
		mCurrentTrackMetaData = node.getAttributeValue("val");
		Log.d(TAG, "mCurrentTrackMetaData="+mCurrentTrackMetaData);
	}
	
	protected void onTransportStateChanged(String before, String after) {
		Log.d(TAG, "onTransportStateChanged:"+friendlyName+":before="+before+":after="+after);
		//TODO:
//		timer.schedule(task, delay, period)
		//if playing && start timer
		//if not playing, stop timer
		if (!"PLAYING".equals(after)) {
			Log.d(TAG, "onTransportStateChanged:timer.purge()");
			timer.purge();			
			return;
		}
		
		if (!after.equals(before)) {
			Log.d(TAG, "onTransportStateChanged:timer.schedule()");
			positionTask = new PositionTimerTask();
			timer.schedule(positionTask, 0, 1000);
		}
	}
	
	public boolean init(ITEMNODE_TYPE type, int pos, ArrayList<Node> nodeList) {
		//TODO:if others controlling this renderer
		//return false here
		this.currentPlayingType = type;
		this.currentPlayingPos = pos;
		this.mediaServerNodeList = nodeList;
		return true;		
	}
	
	public boolean uninit() {
		this.currentPlayingType = null;
		this.currentPlayingPos = 0;
		this.mediaServerNodeList = null;
		return true;
	}
	
	public void operate(MEDIATOR_OPERATION operation, int intParam) {
		Log.d(TAG, "operation="+operation+":intParam="+intParam);
		switch (operation) {
		case PLAYPAUSE:
			if (CURRENT_POSITION != intParam) {
				this.currentPlayingPos = intParam;
				play(false);
			}
			else {
				if ("PLAYING".equals(this.mTransportState))
					pause();
				else
					play(true);
			}
			break;
		case NEXT:
			next();
			break;
		case PREV:
			prev();
			break;
		case SEEK:
			if (ITEMNODE_TYPE.LOCAL_IMAGE == this.currentPlayingType) {
				this.currentPlayingPos = intParam;
				play(false);				
			} 
			else {
				seek(intParam);
			}
			break;
		case STOP:
			stop();
			break;
		default:
			break;
		}
	}
	
	protected void seek(int second) {
		SeekTask task = new SeekTask();
		task.execute(second);
	}

	protected void pause() {
		PauseTask task = new PauseTask();
		task.execute();
	}
	
	protected void play(boolean pauseContinue) {
		Log.d(TAG, "play");
				
		JoinusControlPoint cp = JoinusControlPoint.getInstance();		
		
		PlayTask task = new PlayTask();
		if (pauseContinue) {
			task.execute(new ItemNode[]{null});
			return;
		}
		
		String fileName, url, protocolInfo, title;		
		
		switch (currentPlayingType) {
		case LOCAL_IMAGE:
			fileName = LocalMediaDataManager.getInstance().getImageList().get(currentPlayingPos).filePathName;
			url = MicroHTTPD.getLocalImageUrl(currentPlayingPos);
			protocolInfo = cp.getProtocolInfo(url, fileName);
			updateListenerDuration(-1, null, (currentPlayingPos+1)+"");
			task.execute(cp.getItemNode(fileName, url, protocolInfo, JoinusControlPoint.UPNP_CLASS_IMAGE_PHOTO));
			title = fileName;
			break;
		case LOCAL_AUDIO:
			fileName = LocalMediaDataManager.getInstance().getMusicList().get(currentPlayingPos).filePathName;
			url = MicroHTTPD.getLocalAudioUrl(currentPlayingPos);
			protocolInfo = cp.getProtocolInfo(url, fileName);
			task.execute(cp.getItemNode(fileName, url, protocolInfo, JoinusControlPoint.UPNP_CLASS_AUDIO_MUSIC));
			title = fileName;
			break;
		case LOCAL_VIDEO:
			fileName = LocalMediaDataManager.getInstance().getVideoList().get(currentPlayingPos).filePathName;
			title = LocalMediaDataManager.getInstance().getVideoList().get(currentPlayingPos).title;
			url = MicroHTTPD.getLocalVideoUrl(currentPlayingPos);
			protocolInfo = cp.getProtocolInfo(url, fileName);
			task.execute(cp.getItemNode(fileName, url, protocolInfo, JoinusControlPoint.UPNP_CLASS_VIDEO_MOVIE));
			break;
		default:
			break;
		}
		
		return;
	}

	protected boolean next() {
		Log.d(TAG, "next");
				
		int total = getCurrentListSize();
		
		if (this.currentPlayingPos+1 >= total)
			return false;
		
		this.currentPlayingPos++;
		play(false);
		
		return true;
	}
	
	protected boolean prev() {
		Log.d(TAG, "prev");
				
		if (this.currentPlayingPos-1 < 0)
			return false;
		
		this.currentPlayingPos--;
		play(false);
		
		return true;
	}
	
	protected int getCurrentListSize() {
		switch (currentPlayingType) {
		case LOCAL_IMAGE:
			return LocalMediaDataManager.getInstance().getImageList().size();
		case LOCAL_AUDIO:
			return LocalMediaDataManager.getInstance().getMusicList().size();
		case LOCAL_VIDEO:
			return LocalMediaDataManager.getInstance().getVideoList().size();
		case EXTERNAL_ITEMNODE:
			return this.mediaServerNodeList.size();
		}
		
		return 0;		
	}
	
	protected void updatePrevNextButton() {
		int itemCount = getCurrentListSize();		
		
		updateListenerNextButton(this.currentPlayingPos<(itemCount-1));
		updateListenerPrevButton(this.currentPlayingPos>0);
	}

	protected boolean stop() {
		Log.d(TAG, "stop");
				
		StopTask task = new StopTask();
		task.execute();
		
		return true;
	}

	protected class PlayTask extends AsyncTask<ItemNode, Void, Boolean> {

		protected ItemNode node = null;
		@Override
		protected void onPostExecute(Boolean result) {
			// TODO Auto-generated method stub
			updateListenerPlayPauseButton(true, !result);
			updatePrevNextButton();
			updateListenerSeekBar(true);
			if (result&&(null != node))
				updateListenerTitle(node.getTitle());
				
			super.onPostExecute(result);
		}

		@Override
		protected void onPreExecute() {
			// TODO Auto-generated method stub
			updateListenerPlayPauseButton(false, true);
			updateListenerNextButton(false);
			updateListenerPrevButton(false);
			updateListenerSeekBar(false);
			super.onPreExecute();
		}

		@Override
		protected Boolean doInBackground(ItemNode... params) {
			// TODO Auto-generated method stub
			node = params[0];
			return JoinusControlPoint.getInstance().playRenderer(params[0]);
		}
	}
	
	protected class StopTask extends AsyncTask<Void, Void, Boolean> {

		@Override
		protected void onPostExecute(Boolean result) {
			// TODO Auto-generated method stub
			super.onPostExecute(result);
		}

		@Override
		protected void onPreExecute() {
			// TODO Auto-generated method stub
			super.onPreExecute();
		}

		@Override
		protected Boolean doInBackground(Void... params) {
			// TODO Auto-generated method stub
			return JoinusControlPoint.getInstance().stopRenderer();
		}

	}
	
	protected class PauseTask extends AsyncTask<Void, Void, Boolean> {

		@Override
		protected void onPostExecute(Boolean result) {
			// TODO Auto-generated method stub
			updateListenerPlayPauseButton(true, result);
			super.onPostExecute(result);
		}

		@Override
		protected void onPreExecute() {
			// TODO Auto-generated method stub
			updateListenerPlayPauseButton(false, false);
			super.onPreExecute();
		}

		@Override
		protected Boolean doInBackground(Void... params) {
			// TODO Auto-generated method stub			
			return JoinusControlPoint.getInstance().pauseRenderer();
		}
	}

	protected class SeekTask extends AsyncTask<Integer, Void, Boolean> {

		@Override
		protected void onPostExecute(Boolean result) {
			// TODO Auto-generated method stub
			updateListenerSeekBar(true);
			super.onPostExecute(result);
		}

		@Override
		protected void onPreExecute() {
			// TODO Auto-generated method stub
			updateListenerSeekBar(false);
			super.onPreExecute();
		}

		@Override
		protected Boolean doInBackground(Integer... params) {
			// TODO Auto-generated method stub			
			return JoinusControlPoint.getInstance().seekRenderer(params[0]);
		}

	}

	protected void updateInput() {
		if (null == mListener)
			return;
	}	

	protected int convertTimeStringToInt(String time) {
		String s[] = time.split(":");
		if ((s.length < 2)||(s.length > 3))
			return 0;
		
		int second = Integer.parseInt(s[s.length-1]);
		int minute = Integer.parseInt(s[s.length-2]);
		int hour = 0;
		if (3 == s.length)
			hour = Integer.parseInt(s[0]);
		return (hour*3600)+(minute*60)+(second);
	}
	
	protected void updateListenerPlayPauseButton(boolean enabled, boolean isPlay) {
		if (null != mListener)
			mListener.updatePlayPauseButton(enabled, isPlay);
	}
	
	protected void updateListenerNextButton(boolean enabled) {
		if (null != mListener)
			mListener.updateNextButton(enabled);
	}
	
	protected void updateListenerPrevButton(boolean enabled) {
		try {
			lock.readLock().lock();
			if (null != mListener)
				mListener.updatePrevButton(enabled);
		} 
		finally {
			lock.readLock().unlock();
		}
	}
	
	protected void updateListenerSeekBar(boolean enabled) {
		try {
			lock.readLock().lock();
			if (null != mListener)
				mListener.updateSeekBar(enabled);
		} 
		finally {
			lock.readLock().unlock();
		}
	}
	
	protected void updateListenerTitle(String title) {
		try {
			lock.readLock().lock();
			if (null != mListener)
				mListener.updateTitle(title);
		} 
		finally {
			lock.readLock().unlock();
		}
	}
	
	protected void updateListenerStatus(MEDIATOR_STATUS status) {
		try {
			lock.readLock().lock();
			if (null != mListener)
				mListener.updateStatus(status);
		} 
		finally {
			lock.readLock().unlock();
		}
	}
	
	protected void updateListenerDuration(int maxProgress, String duration, String current) {
		try {
			lock.readLock().lock();
			if (null != mListener)
				mListener.updateDuration(maxProgress, duration, current);
		} 
		finally {
			lock.readLock().unlock();
		}
	}
	
	protected void updateListenerProgress(int progress) {
		try {
			lock.readLock().lock();
			if (null != mListener)
				mListener.updateProgress(progress);
		} 
		finally {
			lock.readLock().unlock();
		}
	}

	public interface OnRendererStatusChangedListener {
		public void updatePlayPauseButton(boolean enabled, boolean isPlay);
		public void updateNextButton(boolean enabled);
		public void updatePrevButton(boolean enabled);
		public void updateSeekBar(boolean enabled);
		public void updateTitle(String title);
		public void updateStatus(MEDIATOR_STATUS status);
		public void updateDuration(int maxProgress, String duration, String current);
		public void updateProgress(int progress);
	}
	
}
